#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<assert.h>
#include<errno.h>

#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

#define trimSpace(start) do{\
    while(isspace(*start)) start++;\
}while(0)

char lineCommand[NUM];
char *myargv[OPT_NUM];
int lastCode = 0;
int lastSig = 0;

int redirType = NONE_REDIR;
char* redirFile = NULL;
// "ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
//根据重定向符号分割字符串
void commandCheck(char* commands)
{
    assert(commands);
    char *start = commands;
    char *end = commands + strlen(commands);

    while(start <end)
    {
        if(*start == '>')
        {
            *start = '\0';
            start++;
            if(*start == '>')
            {
                //"ls -a >> file.log"
                redirType = APPEND_REDIR;
                start++;
            }
            else
            {
                redirType = OUTPUT_REDIR;
            }
            trimSpace(start);
            redirFile = start;
            break;
        }
        else if(*start == '<')
        {
            //"cat <   file.log"
            *start = '\0';
            start++;
            trimSpace(start);
            //填写重定向信息
            redirType = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else
        {
            start++;
        }
    }
}

int main()
{
    redirFile = NULL;
    redirType = NONE_REDIR;
    errno = 0;
    while(1)
    {
        //输出提示符
        printf("[sk@alcloud ~] ");
        fflush(stdout);

        //获取用户输入
        char*s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
        assert(s != NULL);
        (void)s;
        //清除最后一个\n
        lineCommand[strlen(lineCommand) - 1] = 0;
        commandCheck(lineCommand);
        //字符串切割
        myargv[0] = strtok(lineCommand, " ");
        int i = 1;
        if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
        {
            myargv[i++] = (char*)"--color=auto";
        }
        while(myargv[i++] = strtok(NULL, " "));
        //如果是cd命令，不需要创建子进程，让shell自己执行对应的命令，本质就是执行系统接口
        if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
        {
            if(myargv[1] != NULL)
            {
                chdir(myargv[1]);
            }
            continue;
        }
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
        {
            if(strcmp(myargv[1], "$?") == 0)
            {
                printf("%d, %d\n", lastCode, lastSig);
            }
            else
            {
                printf("%s\n", myargv[1]);
            }
            continue;
        }
        //测试是否成功
        //for(int i = 0; myargv[i]; i++)
        //{
        //    printf("%d : %s\n", i, myargv[i]);
        //}
        
        //执行命令
        pid_t id = fork();
        assert(id != -1);

        if(id == 0)
        {
            //因为命令是子进程执行的，真正重定向工作一定是子进程来完成的
            //如何重定向，是父进程给子进程提供信息的
            //这里重定向不会影响父进程，进程具有独立性
            switch(redirType)
            {
                case NONE_REDIR:
                    //跳过
                    break;
                case INPUT_REDIR:
                    {
                        int fd =  open(redirFile, O_RDONLY);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        //重定向的文件已经打开了
                        dup2(fd, 0);
                    }
                    break;
                case OUTPUT_REDIR:
                case APPEND_REDIR:
                    {
                        umask(0);
                        int flags = O_WRONLY | O_CREAT;
                        if(redirType == OUTPUT_REDIR)
                        {
                            flags |= O_TRUNC;
                        }
                        else
                        {
                            flags |= O_APPEND;
                        }
                        int fd = open(redirFile, flags, 0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        dup2(fd, 1);
                    }
                    break;
                default:
                    printf("程序出错\n");
                    break;
            }
            execvp(myargv[0], myargv);
            exit(1);
        }
        int status = 0;
        pid_t ret =  waitpid(id, &status, 0);
        assert(ret != -1);
        lastCode = (status >> 8) & 0xff;
        lastSig = status & 0x7f;
    }
}
